﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.ServiceLocation;

namespace Eswat
{
	/// <summary>
	/// Base test runner for test scripts to be implemented by language-specific runners.
	/// </summary>
	public abstract class TestRunnerBase<TResult> : ITestRunner<TResult>
	{
		/// <summary>
		/// Executes the test runner against the specified test context.
		/// </summary>
		/// <typeparam name="TTestContext">The type of the test context.</typeparam>
		/// <param name="testContext">The test context.</param>
		/// <returns>A test runner result.</returns>
		public ITestRunnerResult<TResult, TTestContext> Execute<TTestContext>(
			ITestContext<TTestContext> testContext)
		{
			TestRunnerResult<TResult, TTestContext> testResult = null;

			try
			{
				var loadedAssemblies = GetInMemoryAssemblies();

				this.OnBeforeTest(testContext, loadedAssemblies);

				var result = this.RunTest(testContext);

				testResult = new TestRunnerResult<TResult, TTestContext>(result, testContext);
			}
			catch (Exception exception)
			{
				this.OnException(testContext, exception);

				testResult = new TestRunnerResult<TResult, TTestContext>(exception, testContext);
			}

			return testResult;
		}

		/// <summary>
		/// Called when [before test].
		/// </summary>
		/// <typeparam name="TTestContext">The type of the test context.</typeparam>
		/// <param name="testContext">The test context.</param>
		/// <param name="availableInMemoryAssemblies">The available in-memory assemblies.</param>
		protected abstract void OnBeforeTest<TTestContext>(
			ITestContext<TTestContext> testContext,
			IEnumerable<Assembly> availableInMemoryAssemblies);

		/// <summary>
		/// Runs the test.
		/// </summary>
		/// <typeparam name="TTestContext">The type of the test context.</typeparam>
		/// <param name="testContext">The test context.</param>
		/// <returns>
		/// The test result.
		/// </returns>
		protected abstract TResult RunTest<TTestContext>(
			ITestContext<TTestContext> testContext);

		/// <summary>
		/// Called when [exception].
		/// </summary>
		/// <typeparam name="TTestContext">The type of the test context.</typeparam>
		/// <param name="testContext">The test context.</param>
		/// <param name="exception">The exception.</param>
		protected virtual void OnException<TTestContext>(
			ITestContext<TTestContext> testContext,
			Exception exception)
		{
			var testResultLoggingService = ServiceLocator.Current.GetInstance<ITestResultLoggingService>();

			testResultLoggingService.Results.Add(new TestResult(TestStatus.Failed, "Unexpected Exception", exception.Message + Environment.NewLine + exception.ToString()));
		}

		/// <summary>
		/// Gets the in memory assemblies.
		/// </summary>
		private static IEnumerable<Assembly> GetInMemoryAssemblies()
		{
			var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(asm => !asm.IsDynamic && !string.IsNullOrEmpty(asm.Location));

			return loadedAssemblies;
		}
	}
}
